using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using XNOgre.Core;
using XNOgre.Scene;
using XNOgre.Graphics;


namespace XNOgreSamples
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class CelShading : DrawableGameComponent
    {
        SceneManager scene;
        Camera camera;
        ViewportTarget viewport;

        SceneNode entNode;
        SceneNode lightPivot, lightNode;
        Light light;

        Effect celEffect;
        Texture2D diffuseRamp, specularRamp, edgeRamp;

        public CelShading(Game game)
            : base(game)
        {
        }

        private Shader CreateCelShader(Color diffuse, Color specular)
        {
            ShaderParameter worldParam = new ShaderParameter("World", GpuProgramParameter.WorldMatrix);
            ShaderParameter viewParam = new ShaderParameter("View", GpuProgramParameter.ViewMatrix);
            ShaderParameter projParam = new ShaderParameter("Projection", GpuProgramParameter.ProjectionMatrix);
            ShaderParameter diffuseParam = new ShaderParameter("diffuseRamp", diffuseRamp);
            ShaderParameter specularParam = new ShaderParameter("specularRamp", specularRamp);
            ShaderParameter edgeParam = new ShaderParameter("edgeRamp", edgeRamp);
            ShaderParameter customDiffuse = new ShaderParameter("customDiffuse", diffuse.ToVector4());
            ShaderParameter customSpecular = new ShaderParameter("customSpecular", specular.ToVector4());
            ShaderParameter eyePosition = new ShaderParameter("eyePosition", GpuProgramParameter.CameraPositionInObjectSpace);
            ShaderParameter shininess = new ShaderParameter("shininess", 10.0f);
            ShaderParameter lightPosition = new ShaderParameter("lightPosition", GpuProgramParameter.LightPositionObjectSpace);
            lightPosition.AutoConstant = 0;
            Shader shader = new Shader(celEffect, worldParam, viewParam, projParam, diffuseParam, specularParam, edgeParam, customSpecular, customDiffuse, eyePosition, shininess, lightPosition);
            return shader;
        }
        private Material CreateCelMaterial(Color diffuse, Color specular)
        {
            TextureUnit tu = new TextureUnit()
            {
                TextureName = "CelShading/cel_shading_diffuse",
                TexAddressMode = TextureAddressMode.Clamp,
            };
            Pass pass = new Pass()
            {
                TextureUnit = tu,
                Shader = CreateCelShader(diffuse,specular)
            };
            Technique technique = new Technique()
            {
                Passes = new Pass[] { pass }
            };
            Material celMaterial = new Material("CelShading")
            {
                Techniques = new Technique[] { technique }
            };

            return celMaterial;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            scene = Root.Instance.CreateScene();
            camera = scene.CreateCamera("Cam1");
            viewport = Root.Instance.RenderWindow.AddViewport(camera);

            base.Initialize();
        }
        
        protected override void LoadContent()
        {
            base.LoadContent();

            Random rand = new Random();

            viewport.Color = Color.White;

            //load some textures
            string celDiffuse = "CelShading/cel_shading_diffuse";
            string celEdge = "CelShading/cel_shading_edge";
            string celSpecular = "CelShading/cel_shading_specular";

            diffuseRamp = Root.Instance.Content.Load<Texture2D>(celDiffuse);
            specularRamp = Root.Instance.Content.Load<Texture2D>(celSpecular);
            edgeRamp = Root.Instance.Content.Load<Texture2D>(celEdge);

            celEffect = Root.Instance.Content.Load<Effect>("CelShading/CelShading");
            

            //create our model
            Entity ent = scene.CreateEntity("Head", "CelShading/LaserPistol02");
            entNode = scene.RootSceneNode.CreateChildSceneNode();
            entNode.AttachObject(ent);

            //give each SubEntity their own material
            for (int i = 0; i < ent.NumSubEntites; i++)
            {
                ent.GetSubEntity(i).Material = CreateCelMaterial(new Color(rand.Next(0, 256), rand.Next(0, 256), rand.Next(0, 256)), new Color(rand.Next(0, 256), rand.Next(0, 256), rand.Next(0, 256)));
            }
            //create a basic point light with an offset
            light = scene.CreateLight();
            light.Type = Light.LightType.Point;
            light.Diffuse = Color.Red;
            light.AttenuationRange = 200f;

            lightPivot = scene.RootSceneNode.CreateChildSceneNode();
            lightNode = lightPivot.CreateChildSceneNode(new Vector3(0, 5, 60));
            lightNode.AttachObject(light);


            camera.Translate(new Vector3(0, 15, 15));
            camera.LookAt(Vector3.Zero);
            camera.NearClipDistance = 0.15f;
            
        }
        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            Root.Instance.Render();
        }
        protected override void UnloadContent()
        {
            base.UnloadContent();

            scene.ClearScene();

            diffuseRamp = null;
            specularRamp = null;
            edgeRamp = null;
            celEffect = null;

            camera.Dispose();
            camera = null;

            Root.Instance.RenderWindow.RemoveAllViewports();
        }
        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            //entNode.Yaw(0.01f);
            lightPivot.Yaw(0.01f);
            
            var keyboard = Keyboard.GetState();
            Vector3 translate = Vector3.Zero;
            if (keyboard.IsKeyDown(Keys.W))
            {
                translate += new Vector3(0, 0, -1);
            }
            if (keyboard.IsKeyDown(Keys.S))
            {
                translate += new Vector3(0, 0, 1);
            }
            if (keyboard.IsKeyDown(Keys.A))
            {
                translate += new Vector3(-1, 0, 0);
            }
            if (keyboard.IsKeyDown(Keys.D))
            {
                translate += new Vector3(1, 0, 0);
            }
            if (keyboard.IsKeyDown(Keys.RightShift))
            {
                translate += new Vector3(0, 1, 0);
            }
            if (keyboard.IsKeyDown(Keys.Space))
            {
                translate += new Vector3(0, -1, 0);
            }
            camera.Translate(translate);

            if (keyboard.IsKeyDown(Keys.Left))
            {
                camera.Yaw(0.03f);
            }
            if (keyboard.IsKeyDown(Keys.Right))
            {
                camera.Yaw(-0.03f);
            }
            if (keyboard.IsKeyDown(Keys.Up))
            {
                camera.Pitch(0.03f);
            }
            if (keyboard.IsKeyDown(Keys.Down))
            {
                camera.Pitch(-0.03f);
            }
            if (keyboard.IsKeyDown(Keys.Q))
            {
                camera.Roll(-0.03f);
            }
            if (keyboard.IsKeyDown(Keys.E))
            {
                camera.Roll(0.03f);
            }

            if (keyboard.IsKeyDown(Keys.Escape))
            {
                this.Game.Components.Remove(this);
                this.Dispose();
                (this.Game as Browser).StopSample();
            }

            base.Update(gameTime);
        }
    }
}
